<?php
// +----------------------------------------------------------------------
// | 服务
// +----------------------------------------------------------------------
declare(strict_types=1);

namespace app\service;

use DateTimeImmutable;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Builder;
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\Validator;

/**
 * JWT 生成与认证
 *
 * Class JwtTokenService
 * @package app\service
 */
class JwtTokenService
{

    /**
     * 获取Token过期时间
     * 
     * @return DateTimeImmutable
     */
    public static function getTokenExpiresTime(): DateTimeImmutable
    {
        $now = new DateTimeImmutable();
        return $now->modify('+' . env('jwt.expires_minute') . ' minute');
    }

    /**
     * 获取key密钥
     * 
     * @return InMemory
     */
    private static function getTokenKey(): InMemory
    {
        $secrect = env('jwt.key_secrect');
        if (strlen($secrect) < 32) {
            $secrect = str_pad($secrect, 32, '0');
        }
        return InMemory::plainText($secrect);
    }

    /**
     * 生成Token
     * 
     * @param array $claims 附加字段数组
     *
     * @return string
     */
    public static function issuingToken(array $claims): string
    {

        $tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default()));

        $now   = new DateTimeImmutable();
        $token = $tokenBuilder
            // Configures the issuer (iss claim)
            ->issuedBy(env('jwt.issued_by'))
            // Configures the audience (aud claim)
            ->permittedFor(env('jwt.permitted_for'))
            // Configures the time that the token was issue (iat claim)
            ->issuedAt($now)
            // Configures the expiration time of the token (exp claim)
            ->expiresAt($now->modify('+' . env('jwt.expires_minute') . ' minute'));

        // Add custom claims
        foreach ($claims as $key => $value) {
            $token = $token->withClaim($key, $value);
        }

        // Builds a new token
        $token = $token->getToken(new Sha256(), self::getTokenKey());

        return $token->toString();
    }

    /**
     * 解析Token并获取
     * 失败返回false
     * 
     * @param mixed $jwt token文本 
     * @return array|bool
     */
    public static function parseToken($jwt): array|bool
    {
        if (!$jwt) {
            return false;
        }

        $parser = new Parser(new JoseEncoder());

        try {
            $token = $parser->parse($jwt);

            $validator = new Validator();
            $validator->assert($token, new IssuedBy(env('jwt.issued_by')));
            $validator->assert($token, new PermittedFor(env('jwt.permitted_for')));
            $validator->assert($token, new SignedWith(new Sha256(), self::getTokenKey()));
            $validator->assert($token, new LooseValidAt(SystemClock::fromSystemTimezone()));
        } catch (\Exception $e) {
            return false;
        }
        assert($token instanceof UnencryptedToken);

        return $token->claims()->all();
    }
}
